#include "stdafx.h"
#include <mmsystem.h>
#include "AdpcmCapturerRender.h"
#include "adpcm.h"
#include "amr.h"
#include <memory>
#include <algorithm>
#include "audioencode.h"

//-------------------------------CAudioInputPin---------------------------------

class CAudioInputPin : public CBaseInputPin
{
	friend class CAdpcmCaptureRender;
private:
	CAdpcmCaptureRender *m_pFilter;					// The filter that owns us

	int m_channels;
	int m_sampeRate;
	int m_bitPerSample;
	int m_bitrate;
	AudioCodecId m_codecId;
	CaptureAudioCallBack m_callback;
	HAUDIOSESSION m_sessionId;
	bool m_isEncoderInited;
	std::auto_ptr<AVCodecContext> m_amrEncodeContext;
	USHORT m_remainSamples[320];							//for amr wb, max 320; nb, 160
	UINT m_remainCount;

	adpcm_state m_encodeState;

public:
	CAudioInputPin(CAdpcmCaptureRender *pTextOutFilter,
		HRESULT *phr, LPCWSTR pPinName, HAUDIOSESSION sessionId, CaptureAudioCallBack callback, int channels, int sampleRate, int bitrate, AudioCodecId codecId, int bitsPerSample = 16);
	~CAudioInputPin();

	HRESULT BreakConnect();							// Lets us know where a connection ends
	HRESULT CheckMediaType(const CMediaType *pmt);	// Check that we can support this input type
	HRESULT SetMediaType(const CMediaType *pmt);	// Actually set the current format

	// IMemInputPin virtual methods
	HRESULT Active(void);
	HRESULT Inactive(void);

	// Here's the next block of data from the stream.
	// AddRef it if you are going to hold onto it
	STDMETHODIMP Receive(IMediaSample *pSample);

}; // CAudioInputPin

//-----------------------------CONSTRUCTOR & DESTRUCTOR-------------------------

CAudioInputPin::CAudioInputPin(CAdpcmCaptureRender *pFilter,
							   HRESULT *phr, LPCWSTR pPinName,
							   HAUDIOSESSION sessionId,
							   CaptureAudioCallBack callback, int channels, 
							   int sampleRate, int bitrate, 
							   AudioCodecId codecId, int bitsPerSample)
	:CBaseInputPin(NAME("Scope Input Pin"), pFilter, pFilter, phr, pPinName),
	m_channels(channels),
	m_sampeRate(sampleRate),
	m_bitPerSample(bitsPerSample),
	m_callback(callback),
	m_sessionId(sessionId),
	m_codecId(codecId),
	m_bitrate(bitrate),
	m_isEncoderInited(false),
	m_remainCount(0)
{
	m_pFilter = pFilter;
	memset(&m_encodeState, 0, sizeof(m_encodeState));

	switch (codecId)
	{
	case AUDIOCODECID_AMRNB:
		m_amrEncodeContext.reset(new AVCodecContext);
		memset(m_amrEncodeContext.get(), 0, sizeof(AVCodecContext));
		m_amrEncodeContext->priv_data = new char[sizeof(AMRContext)];
		memset(m_amrEncodeContext->priv_data, 0, sizeof(AMRContext));

		m_amrEncodeContext->codec_id = CODEC_ID_AMR_NB;
		m_amrEncodeContext->sample_rate = 8000;
		m_amrEncodeContext->channels = 1;
		m_amrEncodeContext->bit_rate = bitrate;
		m_amrEncodeContext->sample_fmt = SAMPLE_FMT_S16;

		if (amr_nb_encode_init(m_amrEncodeContext.get()) == 0)
			m_isEncoderInited = true;

		break;

	case AUDIOCODECID_AMRWB:
		m_amrEncodeContext.reset(new AVCodecContext);
		memset(m_amrEncodeContext.get(), 0, sizeof(AVCodecContext));
		m_amrEncodeContext->priv_data = new char[sizeof(AMRWBContext)];
		memset(m_amrEncodeContext->priv_data, 0, sizeof(AMRWBContext));

		m_amrEncodeContext->codec_id = CODEC_ID_AMR_WB;
		m_amrEncodeContext->sample_rate = 16000;
		m_amrEncodeContext->channels = 1;
		m_amrEncodeContext->bit_rate = bitrate;
		m_amrEncodeContext->sample_fmt = SAMPLE_FMT_S16;

		if (amr_wb_encode_init(m_amrEncodeContext.get()) == 0)
			m_isEncoderInited = true;

		break;
	}
}


CAudioInputPin::~CAudioInputPin()
{
	switch (m_codecId)
	{
	case AUDIOCODECID_AMRNB:
		if (m_isEncoderInited)
			amr_nb_encode_close(m_amrEncodeContext.get());

		delete[] (char *) m_amrEncodeContext->priv_data;

		break;

	case AUDIOCODECID_AMRWB:
		if (m_isEncoderInited)
			amr_wb_encode_close(m_amrEncodeContext.get());

		delete[] (char *) m_amrEncodeContext->priv_data;

		break;
	}
}

//------------------------------------------------------------------------------

HRESULT CAudioInputPin::BreakConnect()
{
	// Check we have a valid connection

	if(m_mt.IsValid() == FALSE)
	{
		// Don't return an error here, because it could lead to 
		// ASSERT failures when rendering media files in GraphEdit.
		return S_FALSE;
	}

	m_pFilter->Stop();

	// Reset the CLSIDs of the connected media type

	m_mt.SetType(&GUID_NULL);
	m_mt.SetSubtype(&GUID_NULL);
	return CBaseInputPin::BreakConnect();
} 

HRESULT CAudioInputPin::CheckMediaType(const CMediaType *pmt)
{
	CheckPointer(pmt,E_POINTER);

	WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmt->Format();

	if(pwfx == NULL)
		return E_INVALIDARG;

	// Reject non-PCM Audio type

	if(pmt->majortype != MEDIATYPE_Audio)
	{
		return E_INVALIDARG;
	}

	if(pmt->formattype != FORMAT_WaveFormatEx)
	{
		return E_INVALIDARG;
	}

	if(pwfx->wFormatTag != WAVE_FORMAT_PCM)
	{
		return E_INVALIDARG;
	}

	if (pwfx->nChannels != m_channels 
		|| pwfx->nSamplesPerSec != m_sampeRate 
		|| pwfx->wBitsPerSample != m_bitPerSample
		|| pwfx->nBlockAlign != m_bitPerSample / 8 * m_channels)
	{
		return E_UNEXPECTED;
	}

	return NOERROR;

} // CheckMediaType


HRESULT CAudioInputPin::SetMediaType(const CMediaType *pmt)
{
	CheckPointer(pmt,E_POINTER);
	CAutoLock lock(m_pFilter);

	HRESULT hr = CBaseInputPin::SetMediaType(pmt);
	//if(SUCCEEDED(hr))
	//{
	//	WAVEFORMATEX *pwf = (WAVEFORMATEX *) pmt->Format();
	//}

	return hr;
}

HRESULT CAudioInputPin::Active(void)
{
	return NOERROR;
}


HRESULT CAudioInputPin::Inactive(void)
{
	return NOERROR;
}

//#include <stdio.h>

//FILE *file = fopen("f:\\samples", "wb");
#include <stdio.h>
HRESULT CAudioInputPin::Receive(IMediaSample * pSample)
{
	CAutoLock lock(m_pFilter);

	if(m_pFilter->m_State == State_Stopped)
		return E_FAIL;

	//HRESULT hr = CBaseInputPin::Receive(pSample);
	//if(FAILED(hr))
	//	return hr;

	//the following code for mono 16 bits per sample audio only

	BYTE *sampleData = NULL;
	long sampleCount = pSample->GetActualDataLength();
	pSample->GetPointer(&sampleData);

	//fwrite(sampleData, 1, sampleCount, file);
	//static DWORD lasttime = timeGetTime();
	//DWORD newtime = timeGetTime();
	//char str[128];
	//sprintf(str, "%u,%d\n", newtime - lasttime, sampleCount);
	//OutputDebugString(str);
	//lasttime = newtime;

	if (m_pFilter->m_volume != 100)
	{
		short *samples = (short *) sampleData;
		if (m_pFilter->m_volume > 100)
			m_pFilter->m_volume = 100;
		float volume = float(m_pFilter->m_volume) / float(100.0);
		for (int i = sampleCount / 2; i; --i)
		{
			samples[i - 1] = (short) (float(samples[i - 1]) * volume);
		}
	}

	if (m_codecId == AUDIOCODECID_ADPCM)
	{
		while (sampleCount > 0)
		{
			int toEncodeLen = min(sampleCount, 4000);
			char adpcmData[64 * 1024];
			memset(&m_encodeState, 0, sizeof(m_encodeState));
			adpcm_coder((short *) sampleData, adpcmData, toEncodeLen / 2, &m_encodeState);
			m_callback(m_sessionId, adpcmData, toEncodeLen / 4);

			sampleCount -= toEncodeLen;
			sampleData += toEncodeLen;
		}
	}
	else if(m_codecId == AUDIOCODECID_G711)
	{
		unsigned char tmp[4096*4];
		unsigned char g711Data[4096*2]; 

		long tmpCount = sampleCount/4;
		while(tmpCount > 0)
		{
			int tmpCnt = min(sizeof(tmp)/2, tmpCount);
			for(int i = 0;i < tmpCnt; ++i)
			{
				*((short *)tmp + i) = *((short *)sampleData + i * 2);
			}

			int nEncodeCount = G711::ALawEncode(g711Data, (int16_t*)tmp, tmpCnt*2);
			m_callback(m_sessionId, (char *)g711Data, nEncodeCount);
			tmpCount -= tmpCnt;
		}
	}
	else if (m_codecId == AUDIOCODECID_AMRNB)
	{
		if (!m_isEncoderInited)
			return -1;

		//amr nb assume: 16 bits per sample, 1 channels, 8000Hz
#define AMR_NB_FRAME_SAMPLE_COUNT 160

		BYTE amrBuf[1024];
		int amrCount = 0;
		USHORT *pos = (USHORT *) sampleData;
		UINT count = sampleCount / 2;

		if (m_remainCount != 0)
		{
			UINT copyCount = min(count, AMR_NB_FRAME_SAMPLE_COUNT - m_remainCount);
			memcpy(m_remainSamples + m_remainCount, pos, copyCount * 2);	
			m_remainCount += copyCount;
			pos += copyCount;
			count -= copyCount;
			if (m_remainCount == AMR_NB_FRAME_SAMPLE_COUNT)
			{
				amrCount = amr_nb_encode_frame(m_amrEncodeContext.get(), amrBuf, 1024, m_remainSamples);
				if (amrCount > 0)
					m_callback(m_sessionId, (char *) amrBuf, amrCount);
				m_remainCount = 0;
			}
		}

		while (count >= AMR_NB_FRAME_SAMPLE_COUNT)
		{
			amrCount = amr_nb_encode_frame(m_amrEncodeContext.get(), amrBuf, 1024, pos);
			if (amrCount > 0)
				m_callback(m_sessionId, (char *) amrBuf, amrCount);
			pos += AMR_NB_FRAME_SAMPLE_COUNT;
			count -= AMR_NB_FRAME_SAMPLE_COUNT;
		}

		if (count != 0)
		{
			memcpy(m_remainSamples, pos, count * 2);
			m_remainCount = count;
		}
	}
	else if (m_codecId == AUDIOCODECID_AMRWB)
	{
		if (!m_isEncoderInited)
			return -1;

		//amr wb assume: 16 bits per sample, 1 channels, 16000Hz
#define AMR_WB_FRAME_SAMPLE_COUNT 160

		BYTE amrBuf[1024];
		int amrCount = 0;
		USHORT *pos = (USHORT *) sampleData;
		UINT count = sampleCount / 2;

		if (m_remainCount != 0)
		{
			UINT copyCount = min(count, AMR_WB_FRAME_SAMPLE_COUNT - m_remainCount);
			memcpy(m_remainSamples + m_remainCount, pos, copyCount * 2);	
			m_remainCount += copyCount;
			pos += copyCount;
			count -= copyCount;
			if (m_remainCount == AMR_WB_FRAME_SAMPLE_COUNT)
			{
				amrCount = amr_wb_encode_frame(m_amrEncodeContext.get(), amrBuf, 1024, m_remainSamples);
				if (amrCount > 0)
					m_callback(m_sessionId, (char *) amrBuf, amrCount);
				m_remainCount = 0;
			}
		}

		while (count >= AMR_WB_FRAME_SAMPLE_COUNT)
		{
			amrCount = amr_wb_encode_frame(m_amrEncodeContext.get(), amrBuf, 1024, pos);
			if (amrCount > 0)
				m_callback(m_sessionId, (char *) amrBuf, amrCount);
			pos += AMR_WB_FRAME_SAMPLE_COUNT;
			count -= AMR_WB_FRAME_SAMPLE_COUNT;
		}

		if (count != 0)
		{
			memcpy(m_remainSamples, pos, count * 2);
			m_remainCount = count;
		}
	}
	else
	{
		m_callback(m_sessionId, (char *) sampleData, sampleCount);
	}


//	Sleep(3);

	return NOERROR;
}




//------------------------------CAdpcmCaptureRender-----------------------------

//-----------------------------CONSTRUCTOR & DESTRUCTOR-------------------------

#pragma warning (disable : 4355) //use "this" pointer for initialization of base class

CAdpcmCaptureRender::CAdpcmCaptureRender(LPUNKNOWN pUnk,HRESULT *phr, HAUDIOSESSION sessionId, CaptureAudioCallBack callback, int channels, int sampleRate, int bitrate, AudioCodecId codecId, int bitsPerSample) :
    CBaseFilter(NAME("Oscilloscope"), pUnk, (CCritSec *) this, CLSID_NULL), 
	m_codecId(codecId),
	m_volume(100)
{
    ASSERT(phr);

    m_pInputPin = new CAudioInputPin(this,phr,L"Audio Input Pin", sessionId, callback, channels, sampleRate, bitrate, codecId, bitsPerSample);
    if(m_pInputPin == NULL)
    {
        if (phr)
            *phr = E_OUTOFMEMORY;
    }

	AddRef();
}

CAdpcmCaptureRender::~CAdpcmCaptureRender()
{
    ASSERT(m_pInputPin);
    delete m_pInputPin;
    m_pInputPin = NULL;
}


//------------------------------------------------------------------------------

int CAdpcmCaptureRender::GetPinCount()
{
	return 1;
}

CBasePin *CAdpcmCaptureRender::GetPin(int n)
{
    // We only support one input pin and it is numbered zero
    if(n != 0)
        return NULL;

    return m_pInputPin;
}

STDMETHODIMP CAdpcmCaptureRender::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName)
{
    return CBaseFilter::JoinFilterGraph(pGraph, pName);
}


STDMETHODIMP CAdpcmCaptureRender::Stop()
{
    CAutoLock lock(this);

    if(m_State != State_Stopped)
    {
        // Pause the device if we were running
        if(m_State == State_Running)
        {
            HRESULT hr = Pause();
            if(FAILED(hr))
                return hr;
        }

        HRESULT hr = CBaseFilter::Stop();
        if(FAILED(hr))
            return hr;
    }

    return NOERROR;
} 


STDMETHODIMP CAdpcmCaptureRender::Pause()
{
    CAutoLock lock(this);

    return CBaseFilter::Pause();
}

STDMETHODIMP CAdpcmCaptureRender::Run(REFERENCE_TIME tStart)
{
    CAutoLock lock(this);

    HRESULT hr = CBaseFilter::Run(tStart);
    if(FAILED(hr))
        return hr;

    return NOERROR;
}



